﻿using System;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;

namespace WSU.ChekaParallel.TestThread.Coordinate
{
    public static class TestSemaphore
    {
        /// <summary>
        /// Semaphore is just like mutex, which named semaphore can be used to synchronize processes
        /// so SemaphoreSlim is more light-weight, more fast to be used to synchronize threads
        /// </summary>
        private static void TestSemaphoreSlim()
        {
            // the argument passed is intial available position
            using (SemaphoreSlim semaphore = new SemaphoreSlim(3))
            {
                const int TOTAL = 5;
                Task[] tasks = new Task[TOTAL];

                for (int index = 0; index < TOTAL; ++index)
                    tasks[index] = Task.Factory.StartNew((obj) =>
                        {
                            int id = (int)obj;
                            Console.WriteLine("thread-{0} is waiting to enter, ... ...", id.ToString());

                            semaphore.Wait();
                            Console.WriteLine("****** thread-{0} entered", id.ToString());

                            Thread.Sleep(100 * id);

                            Console.WriteLine("###### thread-{0} exited", id.ToString());
                            semaphore.Release();
                        }, index + 1);// pay attention here, not using captured variable, but passing parameters

                Task.WaitAll(tasks);
            }// using
        }

        public static void WaitAndCheck(this SemaphoreSlim semaphore, bool expectEntered, int expectAvailable)
        {
            bool isentered = semaphore.Wait(0);
            Debug.Assert(expectEntered == isentered);
            Debug.Assert(expectAvailable == semaphore.CurrentCount);
        }

        /// <summary>
        /// copied from MSDN
        /// </summary>
        private static void TestSemaphoreSlimStatus()
        {
            SemaphoreSlim semaphore = new SemaphoreSlim(2);

            semaphore.WaitAndCheck(true, 1);
            semaphore.WaitAndCheck(true, 0);
            semaphore.WaitAndCheck(false, 0);

            semaphore.Release();
            semaphore.WaitAndCheck(true, 0);

            Task atask = Task.Factory.StartNew(() =>
                {
                    Thread.Sleep(1000);
                    semaphore.Release();
                });
            Console.WriteLine("Waiting to enter, ......");
            // different from SemaphoreSlim.Wait, "semaphore.AvailableWaitHandle.WaitOne" will not deduct the available count
            semaphore.AvailableWaitHandle.WaitOne();
            Console.WriteLine("allow enter, but has not entered yet, current available = {0}", semaphore.CurrentCount.ToString());
            Debug.Assert(1 == semaphore.CurrentCount);

            semaphore.WaitAndCheck(true, 0);

            // ----------------------- wait and clean up
            atask.Wait();
            semaphore.Dispose();
            atask.Dispose();
        }

        public static void TestMain()
        {
            // TestSemaphoreSlim();
            TestSemaphoreSlimStatus();
        }
    }
}